"""
该项目的目的是使用通用爬虫框架来抓取cnBeta网站科技类文章

主要抓取的内容包括
文章标题、文章链接、文章发表日期等数据，再将数据保存在本地文件中。

爬虫框架由以下五部分组成。
1、URL管理器：负责管理待爬取的网页URL
2、数据下载器：根据URL下载数据
3、数据分析器：分析筛选下载的数据
4、数据保存器：将筛选出的数据保存到文件或数据库
5、调度器：负责整个系统的调度
"""
# 1、URL管理器
class URLmanager(object):
    def __init__(self):
        # 初始化待爬取URL和已爬取URL集合
        self.new_urls = set()
        self.old_urls = set()

    def save_new_url(self, url):
        # 将单条URL保存到待爬取的集合中
        if url is not None:
            if url not in self.new_urls and url not in self.old_urls:
                print("保存新URL:()".format(url))
                self.new_urls.add(url)

    def save_new_urls(self, url_list):
        # 批量保存URL
        for url in url_list:
            self.save_new_url(url)

    def get_new_url(self):
        # 去除一条未爬取的URL，同时保存到已爬取的URL中
        if self.get_new_url_num() > 0:
            url = self.new_urls.pop()
            self.old_urls.add(url)
            return url
        else:
            return None

    def get_new_url_num(self):
        # 返回未爬取的URL数量
        return len(self.new_urls)

    def get_old_url_num(self):
        # 返回已经爬取的URL数量
        return len(self.old_urls)

# 2、数据下载器
import  requests

class HtmlDownloader:

    def download(self, url):
        # 判断URL是否为空
        if url is None:
            return None
        else:
            print("开始下载数据，网址{0}".format(url))
            response = requests.get(url)
            # 如果请求成功，则返回网页数据，否则返回None
            if response.status_code == 200:
                print("下载数据成功")
                # 指定使用UTF-8编码
                response.encoding = 'utf-8'
                return response.text
            else:
                return None

# 3 、数据分析器
from bs4 import BeautifulSoup
import re
import requests

class HtmlParse:
    # 主输出方法，范湖i提取的URL列表与待保存的数据
    def parse_data(self, page_url, data):
        print("现在开始分析提取数据")
        # 如果待分析的文章的URL或者数据为空，那么不做处理
        if page_url is None or data is None:
            return None
        else:
            soup = BeautifulSoup(data, 'lxml')
            # 分别调用get_uels 与get_data获取数据
            urls = self.get_urls(soup)
            data = self.get_data(page_url, soup)
            return urls, data

    # 提取科技类文章url
    def get_urls(self, soup):
        urls = list()
        # 获取科技类文章地址Tag
        links = soup.select('a[href*="/tech/"]')
        for link in links:
            # 从Tag中提取网址数据
            url = link['href']
            urls.append(url)
        return urls


    def get_data(self, page_url, soup):
        data = {}
        # 将文章的地址、标题、发布日期保存到字典中
        # 文章URL只是使用参数url
        data['url'] = page_url
        # select_one选择符合条件的第一条
        # 获取文章标题
        title = soup.select_one('.cnbeta-article > header > h1')
        # 获取发布日期
        release_date = soup.select_one('.cnbeta-article > header > .meta > span')
        # 将数据保存到一个字典变量中
        data['title'] = title.get_text()
        data['release_date'] = release_date.get_text()
        print("文章url：{0}".format(page_url))
        print("数据url：{0}".format(data))
        return data



# 4、数据保存器
import os
class DataSave:
    # 指定保存的文件路径
    def __init__(self,path):
        self.path = path

    def save(self, data):
        # 判断文件路径是否存在。若不存在，则抛出错误
        if not os.path.exists(self.path):
            raise FileExistsError("文件路径不存在")
        # 将数据写入文件中，已追加形式写入文件
        with open(self.path, 'a') as fp:
            print("开始写入数据")
            # 加上\n换行写入数据
            fp.write(str(data) + '\n')
        fp.close()

# 5、调度器

class Scheduler:
    def __init__(self, path, root_url, count):
        # 初始化各个组件
        self.url_manager = URLmanager()
        self.data_save = DataSave(path)
        self.html_parse = HtmlParse()
        self.downloader = HtmlDownloader()
        self.root_url = root_url
        self.count = count

    def run_spider(self):
        # 先添加一条URL到未爬取URL集合中
        self.url_manager.save_new_urls(self.root_url)
        # 判断，如果未爬取URL集合中还有网址，并且还没有爬取到50篇文章，那么继续爬取

        while self.url_manager.get_new_url_num() and self.url_manager.get_old_url_num() < self.count:
            try:
                # 获取一条未爬取URL
                url = self.url_manager.get_new_url()
                # 下载数据
                response = self.downloader.download(url)
                # 分析数据，返回URL与文章相关数据
                new_urls, data = self.html_parse.parse_data(url, response)
                # 将获取到的URL保存到未爬取URL集合中
                self.url_manager.save_new_url(new_urls)
                # 保存数据到本地文件
                self.data_save.save(data)
                print("已经抓取了{0}篇文章".format(len(self.url_manager.old_urls)))

            except Exception as e:
                print("本篇文章抓取停止， {0}".format(e))


if __name__ == '__main__':
    root_url = "https://www.cnbeta.com/articles/tech/811395.htm"
    save_url = "C:/Users/lenovo/Desktop/file.txt"
    Spider = Scheduler(save_url, root_url, 20)
    Spider.run_spider()
